/*
	Copyright (C) 2011 Salil Bhagurkar

	This file is part of illusion

	illusion is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	illusion is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with illusion.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
 * These headers are only used for the macros/constants defined. No functions
 * from the kernel can be called from here, as we will be in user mode.
 */
#include <klib/lib.h>
#include <apimod/apimod.h>
#include <kernel/errors.h>
#include <kernel/page.h>
#include <kernel/input.h>
#include <kernel/vfs.h>


/*
 * The user mode library provided by APIMOD to any application
 */


static int dummy()
{
	return ENOSUP;
}

uint_t string_length(const char *c)
{
	uint_t len = 0;
	while((*c) != '\0') {
		len++;
		c++;
	}
	return len;
}

bool string_equals(const char *s1, const char *s2)
{
	while(1) {
		if((*s1) != (*s2))
			return false;
		if((*s1) == '\0' && (*s2) == '\0')
			return true;
		else if((*s1) == '\0' || (*s2) == '\0')
			return false;
		s1++;
		s2++;
	}
}

char *string_copy(char *dest, const char *src, uint_t max_len)
{
	char *dest_orig = dest;
	uint_t i = 0;
	while(i < (max_len - 1) && (*src) != '\0') {
		*dest = *src;
		dest++;
		src++;
	}
	if(i == (max_len - 1) || (*src) == '\0')
		*dest = '\0';
	return dest_orig;
}

void *memory_set(void *start, u8 value, uint_t count)
{
	u32 value_l = value | (value << 8) | (value << 16) | (value << 24);
	u32 *start_l = (u32 *)start;
	uint_t count_l = count / sizeof(u32);
	uint_t final = count - count_l * sizeof(u32);
	uint_t i;
	for(i = 0; i < count_l; i++) {
		*start_l++ = value_l;
	}
	char *start_c = (char *)start_l;
	for(i = 0; i < final; i++) {
		*start_c++ = value;
	}
	return start;
}


void *memory_copy(void *dst, void *src, uint_t count)
{
	u32 *dst_l = (u32 *)dst;
	u32 *src_l = (u32 *)src;
	uint_t count_l = count / sizeof(u32);
	uint_t final = count - count_l * sizeof(u32);
	uint_t i;
	for(i = 0; i < count_l; i++) {
		*dst_l++ = *src_l++;
	}
	char *dst_c = (char *)dst_l;
	char *src_c = (char *)src_l;
	for(i = 0; i < final; i++) {
		*dst_c++ = *src_c++;
	}
	return dst;
}

bool memory_equals(void *m1, void *m2, uint_t count)
{
	u8 *m1_c = (u8 *)m1;
	u8 *m2_c = (u8 *)m2;
	for(uint_t i = 0; i < count; i++) {
		if(m1_c[i] != m2_c[i])
			return false;
	}
	return true;
}

int get_version(uint_t *build_number,
			uint_t *build_date)
{
	return _syscall(SYS_VERSION, (u32)build_number,
			(u32)build_date, 0, 0, 0);
}

int release(uint_t reason)
{
	return _syscall(SYS_RELEASE, reason, 0, 0, 0, 0);
}


int print(char *stream, uint_t length)
{
	return _syscall(SYS_PRINT, (u32)stream, length, 0, 0, 0);
}

int read_input(uint_t *key, uint_t *state)
{
	return _syscall(SYS_READINPUT, (u32)key,
			(u32)state, 0, 0, 0);
}

int open(char *path, uint_t state, uint_t *fd)
{
	return _syscall(SYS_OPEN, (u32)path, state,
			(u32)fd, 0, 0);
}

int change_state(uint_t fid, uint_t new_state)
{
	return _syscall(SYS_CHANGESTATE, fid, new_state, 0, 0, 0);
}

int get_child(uint_t fid, uint_t *child_fid)
{
	return _syscall(SYS_GETCHILD, fid, (u32)child_fid,
			0, 0, 0);
}

int next_child(uint_t fid)
{
	return _syscall(SYS_NEXTCHILD, fid, 0, 0, 0, 0);
}

/*
 * This requires syscall_try to be set, or this will fail, if the child
 * was not found.
 */
int find_child(uint_t fid, uint_t *child_fid, char *name)
{
	uint_t child;
	int err = get_child(fid, &child);
	while(!err) {
		struct sys_info info;
		get_info(child, &info);
		if(string_equals(info.name, name)) {
			*child_fid = child;
			return 0;
		}
		err = next_child(child);
	}
	return ENOENT;
}

int get_parent(uint_t fid, uint_t *parent_fid)
{
	return _syscall(SYS_GETPARENT, fid, (u32)parent_fid,
			0, 0, 0);
}

int close(uint_t fid)
{
	return _syscall(SYS_CLOSE, fid, 0, 0, 0, 0);
}

int read(uint_t fid, void *buf, uint_t offset,
		uint_t count)
{
	return _syscall(SYS_READ, fid, (u32)buf, offset, count, 0);
}

int write(uint_t fid, void *buf, uint_t offset,
		uint_t count)
{
	return _syscall(SYS_WRITE, fid, (u32)buf, offset, count, 0);
}

int create(uint_t fid, char *name)
{
	return _syscall(SYS_CREATE, fid, (u32)name, 0, 0, 0);
}

int get_info(uint_t fid, struct sys_info *info)
{
	return _syscall(SYS_GETINFO, fid, (u32)info, 0, 0, 0);
}

int create_process(uint_t *pid, char *path, char *arguments)
{
	return _syscall(SYS_CREATEPROCESS, (u32)pid, (u32)path, (u32)arguments,
			0, 0);
}


int pause_process(uint_t pid)
{
	return _syscall(SYS_PAUSEPROCESS, pid, 0, 0, 0, 0);
}

int resume_process(uint_t pid)
{
	return _syscall(SYS_RESUMEPROCESS, pid, 0, 0, 0, 0);
}

int printf(const char *fmt, ...)
{
	char buf[512];
	uint_t sz = 512;
	va_list args;
	uint_t ret_val;
	va_start(args, fmt);
	ret_val = vsnprintf(buf,sz,fmt,args);
	print(buf, ret_val);
	va_end(args);
	return ret_val;
}

int insane(int code, const char *fmt, ...)
{
	char buf[512];
	uint_t sz = 512;
	va_list args;
	uint_t ret_val;
	va_start(args, fmt);
	printf("[%d][APIMOD insanity]: ", code);
	ret_val = vsnprintf(buf,sz,fmt,args);
	print(buf, ret_val);
	va_end(args);
	exit_process();
	return 0;
}



void read_line(char *buf, uint_t buf_len)
{
	uint_t i = 0, key;
	uint_t state;
	while(1) {
		read_input(&key, &state);
		if(state == KEYSTATE_DOWN) {
			char c;
			if(key < KEY_NONASCII) {
				c = key;
			} else {
				if(key == KEY_SPACE)
					c = ' ';
				else if(key == KEY_ENTER) {
					buf[i] = '\0';
					printf("\n");
					break;
				} else if(key == KEY_TAB)
					c = '\t';
				else if(key == KEY_BACKSPACE) {
					//Clear one character
					if(i > 0) {
						i--;
						buf[i] = '\0';
						printf("\n%s", buf);
					}
					continue;
				} else
					continue;
			}
			if(i < (buf_len - 1)) {
				printf("%c", c);
				buf[i] = c;
				i++;
			}
		}
	}
}

static char *skip_initial(char *str, char delimiter)
{
	while((*str) != '\0') {
		if((*str) == delimiter)
			str++;
		else
			break;
	}
	return str;
}

char *separate(char *out, char *_str, char delimiter)
{
	char *str = skip_initial(_str, delimiter);
	while((*str) != '\0') {
		if((*str) == delimiter) {
			break;
		} else {
			*out++ = *str;
		}
		str++;
	}
	*out = '\0';
	return str;
}

char **get_children(char *path, uint_t *nr_children)
{
	uint_t fd, child_fd;
	int err = open(path, STATE_READ, &fd);
	if(err)
		return null;
	struct sys_info info;
	get_info(fd, &info);
	char **arr = (char **)malloc(info.child_count * sizeof(char *));
	uint_t i = 0;
	err = get_child(fd, &child_fd);
	while(err != 0) {
		arr[i] = (char *)malloc(VFS_NAME_LEN);
		get_info(child_fd, &info);
		string_copy(arr[i], info.name, VFS_NAME_LEN);
		i++;
		err = next_child(child_fd);
	}
	close(fd);
	*nr_children = info.child_count;
	return arr;
}

void free_children(char **arr, uint_t nr_children)
{
	uint_t i;
	for(i = 0; i < nr_children; i++)
		free(arr[i], VFS_NAME_LEN);
	free(arr, sizeof(char *));
}

int delete_file(char *path)
{
	uint_t fd_child, fd_parent;
	int err = open(path, STATE_WRITE, &fd_child);
	if(err)
		return err;
	err = get_parent(fd_child, &fd_parent);
	if(err) {
		close(fd_child);
		return err;
	}
	err = change_state(fd_parent, STATE_WRITE);
	if(err) {
		close(fd_child);
		close(fd_parent);
		return err;
	}
	err = delete(fd_child, fd_parent);
	close(fd_parent);
	if(err) {
		close(fd_child);
		return err;
	}
	return 0;
}

char *read_all_file(uint_t *length, char *path)
{
	uint_t fd;
	int err = open(path, STATE_READ, &fd);
	if(err)
		return null;
	struct sys_info info;
	get_info(fd, &info);
	char *data = (char *)malloc(info.length);
	err = read(fd, data, 0, info.length);
	close(fd);
	if(err) {
		free(data, info.length);
		return null;
	}
	*length = info.length;
	return data;
}

int write_all_file(char *path, char *data, uint_t length)
{
	uint_t fd;
	int err = open(path, STATE_WRITE, &fd);
	if(err)
		return err;
	err = write(fd, data, 0, length);
	if(err) {
		close(fd);
		return err;
	}
	err = close(fd);
	return err;
}

int create_file(char *path, char *name)
{
	uint_t fd;
	int err = open(path, STATE_WRITE, &fd);
	if(err)
		return err;
	err = create(fd, name);
	close(fd);
	return err;
}

int get_info1(uint_t fid, struct sys_info1 *info)
{
	return _syscall(SYS_GETINFO1, fid, (u32)info, 0, 0, 0);
}

int delete(uint_t fid, uint_t parent_fid)
{
	return _syscall(SYS_DELETE, fid, parent_fid, 0, 0, 0);
}

int get_current(uint_t *fid)
{
	return _syscall(SYS_GETCURRENT, (u32)fid, 0, 0, 0, 0);
}

int set_current(uint_t fid)
{
	return _syscall(SYS_SETCURRENT, fid, 0, 0, 0, 0);
}

int exit_process()
{
	return _syscall(SYS_EXITPROCESS, 0, 0, 0, 0, 0);
}

int string_starts_with(char *str, char *with)
{
	while((*with) != '\0') {
		if((*str) != (*with))
			return 0;
		str++;
		with++;
	}
	return 1;
}

void string_to_lower(char *src)
{
	while((*src) != '\0') {
		char c = *src;
		if(c >= 'A' && c <= 'Z')
			*src = c - 'A' + 'a';
		src++;
	}
}

void string_to_higher(char *src)
{
	while((*src) != '\0') {
		char c = *src;
		if(c >= 'a' && c <= 'a')
			*src = c - 'a' + 'A';
		src++;
	}
}

int string_ends_with(char *str, char *with)
{
	str += string_length(str) - 1;
	uint_t len = string_length(with) - 1;
	with += len;
	while(len--) {
		if((*str) != (*with))
			return 0;
		str--;
		with--;
	}
	return 1;
}

char *string_cat(char *dst, uint_t dst_buf_len, char *src)
{
	char *dst_max = dst + dst_buf_len;
	dst += string_length(dst);
	while((*src) != '\0' && dst < (dst_max - 1)) {
		*dst++ = *src++;
	}
	*dst = '\0';
	return dst;
}

char *string_replace(char *out, size_t out_len, char *str, char *what, char *with)
{
	size_t with_len = string_length(with);
	size_t what_len = string_length(what);
	while((*str) != '\0' && out_len > 1) {
		if(string_equals(str, what)) {
			if(out_len > with_len) {
				string_copy(out, with, out_len);
				out_len -= with_len;
				out += with_len;
				str += what_len;
				continue;
			}
		}
		*out = *str;
		out_len--;
		out++;
		str++;
	}
	*out = '\0';
	return out;
}

char *get_arguments(uint_t *len)
{
	int err = _syscall(SYS_ARGS, 0, (u32)len, 0, 0, 0);
	if(err || (*len) == 0)
		return null;
	char *args = (char *)malloc(*len);
	err = _syscall(SYS_ARGS, (u32)args, (u32)len, 0, 0, 0);
	if(err) {
		free(args, *len);
		return null;
	}
	return args;
}


int get_device(uint_t *fid)
{
	return _syscall(SYS_GETDEV, (u32)fid, 0, 0, 0, 0);
}

int next_device(uint_t fid)
{
	return _syscall(SYS_NEXTDEV, fid, 0, 0, 0, 0);
}

void *get_pages(uint_t count)
{
	void *pages;
	int err = _syscall(SYS_GETPAGES, (u32)(&pages), count, 0, 0, 0);
	if(!err)
		return pages;
	return null;
}

int free_pages(void *pages, uint_t count)
{
	return _syscall(SYS_FREEPAGES, (u32)pages, count, 0, 0, 0);
}

#define MAX_MULTIPLIER 1000000000
#define MAX_HEX_MULTIPLIER 0x10000000

uint_t dec_string(char *_str)
{
	uint_t len = string_length(_str);
	char *str = _str + len - 1;
	uint_t number = 0, multiplier = 1;
	while(str >= _str && multiplier < MAX_MULTIPLIER) {
		if((*str) >= '0' && (*str) <= '9') {
			number += ((*str) - '0') * multiplier;
			multiplier *= 10;
		}
		str--;
	}
	return number;
}

/*
 * Note that the string will include a '0' and 'x' at the beginning
 */
uint_t hex_string(char *_str)
{
	//Move past the '0x'
	_str += 2;
	uint_t len = string_length(_str);
	char *str = _str + len - 1;
	uint_t number = 0, multiplier = 1;
	while(str >= _str && multiplier <= MAX_HEX_MULTIPLIER) {
		if((*str) >= '0' && (*str) <= '9') {
			number += ((*str) - '0') * multiplier;
			multiplier *= 16;
		} else if((*str) >= 'a' && (*str) <= 'f') {
			number += ((*str) - 'a' + 10) * multiplier;
			multiplier *= 16;
		} else if((*str) >= 'A' && (*str) <= 'F') {
			number += ((*str) - 'A' + 10) * multiplier;
			multiplier *= 16;
		}
		str--;
	}
	return number;
}

int wait_for_process(uint_t pid)
{
	return _syscall(SYS_WAITFORPROCESS, pid, 0, 0, 0, 0);
}

int kill_process(uint_t pid)
{
	return _syscall(SYS_KILLPROCESS, pid, 0, 0, 0, 0);
}

int redirect_out(uint_t pid, uint_t fid)
{
	return _syscall(SYS_REDIRECTOUT, pid, fid, 0, 0, 0);
}

int foo()
{
	return _syscall(SYS_FOO, 0, 0, 0, 0, 0);
}

int foo_enter()
{
	return _syscall(SYS_FOO_ENTER, 0, 0, 0, 0, 0);
}

int foo_leave()
{
	return _syscall(SYS_FOO_LEAVE, 0, 0, 0, 0, 0);
}

int try()
{
	return _syscall(SYS_TRY, 0, 0, 0, 0, 0);
}

int exit_try()
{
	return _syscall(SYS_EXITTRY, 0, 0, 0, 0, 0);
}

static struct apimod_base apimod_base = {
	.try = try,
	.exit_try = exit_try,
	.foo = foo,
	.foo_enter = foo_enter,
	.foo_leave = foo_leave,
	.get_version = get_version,
	.exit_process = exit_process,
	.release = release,
	.create_process = create_process,
	.get_current = get_current,
	.set_current = set_current,
	.get_arguments = get_arguments,
	.pause_process = pause_process,
	.resume_process = resume_process,
	.get_pages = get_pages,
	.free_pages = free_pages,
	.wait_for_process = wait_for_process,
	.kill_process = kill_process,
	.redirect_out = redirect_out,
};

static struct apimod_console apimod_console = {
	.printf = printf,
	.print = print,
	.read_input = read_input,
	.read_line = read_line,
};

static struct apimod_lib_string apimod_lib_string = {
	.length = string_length,
	.equals = string_equals,
	.copy = string_copy,
	.cat = string_cat,
	.separate = separate,
	.starts_with = string_starts_with,
	.ends_with = string_ends_with,
	.replace = string_replace,
	.vsnprintf = vsnprintf,
	.snprintf = snprintf,
	.to_lower = string_to_lower,
	.to_higher = string_to_higher,
	.dec_string = dec_string,
	.hex_string = hex_string,
};

static struct apimod_lib_memory apimod_memory = {
	.copy = memory_copy,
	.set = memory_set,
	.malloc = malloc,
	.free = free,
	.equals = memory_equals,
};

static struct apimod_fs apimod_fs = {
	.open = open,
	.close = close,
	.read = read,
	.write = write,
	.get_info = get_info,
	.change_state = change_state,
	.get_child = get_child,
	.next_child = next_child,
	.find_child = find_child,
	.get_parent = get_parent,
	.get_info1 = get_info1,
	.create = create,
	.delete = delete,
	.get_device = get_device,
	.next_device = next_device,
};

static struct apimod_fs1 apimod_fs1 = {
	.get_children = get_children,
	.free_children = free_children,
	.read_all_file = read_all_file,
	.write_all_file = write_all_file,
	.create_file = create_file,
	.delete_file = delete_file,
};

static struct apimod_ini apimod_ini = {
	.read = ini_read,
	.dispose = ini_dispose,
	.get_value = ini_get_value,
	.get_class = ini_get_class,
};

static struct apimod_string apimod_string = {
	.init = string_init,
	.append_char = string_append_char,
	.append = string_append,
	.init_from = string_init_from,
	.new = string_new,
	.deinit = string_deinit,
	.delete = string_delete,
};


/*
 * This is called by a user mode application to get any of the interfaces
 * defined above
 */
int apimod_get_interface(char *name, void **interface)
{
	if(string_equals(name, "console")) {
		*interface = &apimod_console;
	} else if(string_equals(name, "base")) {
		*interface = &apimod_base;
	} else if(string_equals(name, "lib_string")) {
		*interface = &apimod_lib_string;
	} else if(string_equals(name, "fs")) {
		*interface = &apimod_fs;
	} else if(string_equals(name, "memory")) {
		*interface = &apimod_memory;
	} else if(string_equals(name, "ini")) {
		*interface = &apimod_ini;
	} else if(string_equals(name, "fs1")) {
		*interface = &apimod_fs1;
	} else if(string_equals(name, "string")) {
		*interface = &apimod_string;
	} else {
		insane(EPARAM, "apimod_get_interface: Invalid interface requested: %s\n", name);
	}
	return 0;
}
